#include "common.h"
/**
* Generates response message for client
* @param cmd Current command
* @param state Current connection state
*/

void response(Command *cmd, State *state)
{
    switch(lookup_cmd(cmd->command)){
    case USER: ftp_user(cmd,state); break;
    case PASS: ftp_pass(cmd,state); break;
    case PASV: ftp_pasv(cmd,state); break;
    case LIST: ftp_list(cmd,state); break;
    case CWD:  ftp_cwd(cmd,state); break;
    case PWD:  ftp_pwd(cmd,state); break;
    case MKD:  ftp_mkd(cmd,state); break;
    case RMD:  ftp_rmd(cmd,state); break;
    case RETR: ftp_retr(cmd,state); break;
    case STOR: ftp_stor(cmd,state); break;
    case DELE: ftp_dele(cmd,state); break;
    case SIZE: ftp_size(cmd,state); break;
    case ABOR: ftp_abor(state); break;
    case QUIT: ftp_quit(state); break;
    case TYPE: ftp_type(cmd,state); break;
    case RNFR: ftp_rnfr(cmd,state); break;
    case RNTO: ftp_rnto(cmd,state); break;
    case PORT: ftp_port(cmd,state); break;
    case NOOP: ftp_noop(cmd,state); break;
    default:
        state->message = "500 Unknown command\n";
        write_state(state);
        break;
    }
}

/**
* Handle USER command
* @param cmd Command with args
* @param state Current client connection state
*/
void ftp_user(Command *cmd, State *state)
{
    const int total_usernames = sizeof(usernames)/sizeof(char *);
    if(lookup(cmd->arg,usernames,total_usernames)>=0){
        memset(state->username,0,USERNAME_LENGTH);
        strcpy(state->username,cmd->arg);
        state->username_ok = 1;
        state->message = "331 User name okay, need password\n";
    }else{
        state->username = NULL;
        state->username_ok = 0;
        state->logged_in = 0;
        state->message = "530 Invalid username\n";
    }
    write_state(state);
}

/** PASS command */
void ftp_pass(Command *cmd, State *state)
{
    if(state->username_ok!=1){
        state->message = "500 Invalid username \n";
        write_state(state);
        return;
    }

    int cmp = strcmp(state->username,"root");
    if(cmp==0){
        int cmpPsw = strcmp(cmd->arg,"000");
        if(cmpPsw == 0){
            state->logged_in = 1;
            state->message = "230 Login successful\n";
        }else{
            state->logged_in = 0;
            state->message = "500 Invalid password\n";
        }
    }else{
        state->logged_in = 1;
        state->message = "230 Login successful\n";
    }
    write_state(state);
}

/** PASV command */
void ftp_pasv(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,false,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    int ip[4];
    char buff[255];
    char *response = "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\n";
    Port *port = malloc(sizeof(Port));
    gen_port(port);
    getip(state->connection,ip);//get server side ip

    /* Start listening here */
    state->sock_pasv = create_socket((256*port->p1)+port->p2);
    printf("port: %d\n",256*port->p1+port->p2);
    sprintf(buff,response,ip[0],ip[1],ip[2],ip[3],port->p1,port->p2);
    state->message = buff;
    state->mode = SERVER;
    puts(state->message);
    free(port);
    write_state(state);
}

/** LIST command */
void ftp_list(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,false,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    struct dirent *entry;
    struct stat statbuf;
    struct tm *time;
    char timebuff[80], current_dir[BSIZE];
    int connection;
    int data_traffic = 0;
    time_t rawtime;
    struct timeval start_time, end_time;

    /* TODO: dynamic buffering maybe? */
    char cwd[BSIZE], cwd_orig[BSIZE];
    memset(cwd,0,BSIZE);
    memset(cwd_orig,0,BSIZE);

    /* Later we want to go to the original path */
     getcwd(cwd_orig,BSIZE);

    /* Just chdir to specified path */
    if(strlen(cmd->arg)>0 && cmd->arg[0]!='-'){
        chdir(cmd->arg);
    }

    getcwd(cwd,BSIZE);
    DIR *dp = opendir(cwd);

    if(!dp)
    {
        state->message = "550 Failed to open directory.\n";
    }
    else
    {
        if(state->mode == SERVER)
        {
            connection = accept_connection(state->sock_pasv);
            state->message = "150 Here comes the directory listing.\n";
            puts(state->message);
            list_proc(dp,connection);  // transmission list and record the time
            write_state(state);
            state->message = "226 Directory send OK.\n";
            state->mode = NORMAL;
            close(connection);
            close(state->sock_pasv);

        }
        else if(state->mode == CLIENT)
        {
            connection = connect_clint_data_socket(state, state->sock_active); // PORT 模式
            if (connection < 0)
            {
                state->message = "425 No TCP connection was established.\r\n";
            }
            else
            {
                state->message = "150 Here comes the directory listing.\n";
                puts(state->message);
                list_proc(dp,connection);  // transmission list and record the time
                write_state(state);
                state->message = "226 Directory send OK.\n";
                state->mode = NORMAL;
                close(connection);
                close(state->sock_active);
            }
        }
        else
        {
            state->message = "425 MODE ERROR.\n";
        }
    }
    closedir(dp);
    chdir(cwd_orig);
    state->mode = NORMAL;
    write_state(state);
}

/** QUIT command */
void ftp_quit(State *state)
{
    state->message = "221 Goodbye, friend.\n";
    write_state(state);
    close(state->connection);
}

/** PWD command */
void ftp_pwd(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,false,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    char cwd[BSIZE];
    char result[BSIZE];
    memset(result, 0, BSIZE);
    if(getcwd(cwd,BSIZE)!=NULL){
        strcat(result,"257 \"");
        strcat(result,cwd);
        strcat(result,"\"\n");
        state->message = result;
    }else{
        state->message = "550 Failed to get pwd.\n";
    }
    write_state(state);

}

/** CWD command */
void ftp_cwd(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,false,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    if(chdir(cmd->arg) == 0){
        state->message = "250 Directory successfully changed.\n";
    }else{
        state->message = "550 Failed to change directory.\n";
    }

    write_state(state);

}

/**
* MKD command
* TODO: full path directory creation
*/
void ftp_mkd(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,true,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    char cwd[BSIZE];
    char res[BSIZE];
    memset(cwd,0,BSIZE);
    memset(res,0,BSIZE);
    getcwd(cwd,BSIZE);

    /* TODO: check if directory already exists with chdir? */

    /* Absolute path */
    if(cmd->arg[0]=='/'){
        if(mkdir(cmd->arg,S_IRWXU)==0){
            strcat(res,"257 \"");
            strcat(res,cmd->arg);
            strcat(res,"\" new directory created.\n");
            state->message = res;
        }else{
            state->message = "550 Failed to create directory. Check path or permissions.\n";
        }
    }
    /* Relative path */
    else{
        if(mkdir(cmd->arg,S_IRWXU)==0){
            sprintf(res,"257 \"%s/%s\" new directory created.\n",cwd,cmd->arg);
            state->message = res;
        }else{
            state->message = "550 Failed to create directory.\n";
        }
    }
    write_state(state);
}

/** RETR command 下载文件*/
void ftp_retr(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,true,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    int connection;
    int fd,res;
//    FILE* fp;
    struct stat stat_buf;
    struct timeval start_time,end_time;
    off_t offset = 0;
    int sent_total=0, traffic= 0;

    int write_buffer[100];

    gettimeofday(&start_time,0);
    /* Passive mode */
    if(state->mode == SERVER){
        if(!(access(cmd->arg,R_OK)==0 && (fd = open(cmd->arg,O_RDONLY)))) {
            state->message = "550 Failed to get file\n";
            write_state(state);
            close(fd);
            close(connection);
            return;
        }

        fstat(fd,&stat_buf);
        state->message = "150 Opening BINARY mode data connection.\n";
        write_state(state);

        connection = accept_connection(state->sock_pasv);

        close(state->sock_pasv); // Accept only one data connection

        if(delay_download( fd, connection)!=-1){
            state->message = "226 File send OK.\n";
        }else{
            state->message = "550 Fail to transfer file.\n";
        }

    }else if(state->mode == CLIENT)
    {
        if(access(cmd->arg,R_OK)==0 && (fd = open(cmd->arg,O_RDONLY)))
        {
            fstat(fd,&stat_buf);
            state->message = "150 Opening BINARY mode data connection.\n";
            write_state(state);

            connection = connect_clint_data_socket(state, state->sock_active); // PORT
            if (connection < 0)
            {
                state->message = "425 No TCP connection was established.\r\n";
            }
            else
            {
                if(delay_download(fd, connection)!=-1)
                {
                    state->message = "226 File send OK.\n";
                }
                else
                {
                    state->message = "550 Fail to transfer file.\n";
                }
            }
        }
        else
        {
            state->message = "550 Failed to get file\n";
        }
    }

    close(fd);
    close(connection);
    write_state(state);
}

/** Handle STOR command.*/
void ftp_stor(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,false,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    int connection, fd;
    off_t offset = 0;
    int pipefd[2];
    int res = 1;
    const int buff_size = 8192;
    struct timeval start_time, end_time;

    FILE *fp = fopen(cmd->arg,"w");

    if(fp==NULL){
        /* TODO: write status message here! */
        state->message = "550 Failed to get file\n";
        write_state(state);
        return;
    }

    if(state->mode==CLIENT)
    {
        fd = fileno(fp);
        connection = connect_clint_data_socket(state, state->sock_active); // PORT

        if (connection < 0) {
            state->message = "425 No TCP connection was established.\r\n";
        } else {
            state->message = "125 Data connection already open; transfer starting.\n";
            write_state(state);
            if(delay_upload(fd,connection)!=-1){
                state->message = "226 File send OK.\n";
            }else{
                state->message = "550 Fail to transfer file.\n";
            }
        }
        close(state->sock_active);

    }
    /* Passive mode */
    else if(state->mode ==SERVER)
    {
        fd = fileno(fp);
        connection = accept_connection(state->sock_pasv);
        close(state->sock_pasv);

        state->message = "125 Data connection already open; transfer starting.\n";
        write_state(state);
        if(delay_upload(fd,connection)!=-1){
            state->message = "226 File send OK.\n";
        }else{
            state->message = "550 Fail to transfer file.\n";
        }
    }

    close(fd);
    close(connection);
    write_state(state);
}

/** ABOR command */
void ftp_abor(State *state)
{
    if(state->logged_in){
        state->message = "226 Closing data connection.\n";
        state->message = "225 Data connection open; no transfer in progress.\n";
    }else{
        state->message = "530 Please login with USER and PASS.\n";
    }
    write_state(state);

}

/**
* Handle TYPE command.
* BINARY only at the moment.
*/
void ftp_type(Command *cmd,State *state)
{
    if(state->logged_in){
        if(cmd->arg[0]=='I'){
            state->message = "200 Switching to Binary mode.\n";
        }else if(cmd->arg[0]=='A'){

            /* Type A must be always accepted according to RFC */
            state->message = "200 Switching to ASCII mode.\n";
        }else{
            state->message = "504 Command not implemented for that parameter.\n";
        }
    }else{
        state->message = "530 Please login with USER and PASS.\n";
    }
    write_state(state);
}

/** Handle DELE command */
void ftp_dele(Command *cmd,State *state)
{
    bool isAuth = auth_check(state,true,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    if(unlink(cmd->arg)==-1){
        state->message = "550 File unavailable.\n";
    }else{
        state->message = "250 Requested file action okay, completed.\n";
    }

    write_state(state);
}

/** Handle RMD */
void ftp_rmd(Command *cmd, State *state)
{
    bool isAuth = auth_check(state,true,true);
    if(!isAuth){
        write_state(state);
        return;
    }

    char cmd_rm[512];
    snprintf(cmd_rm,512,"rm -rf %s",cmd->arg);//change by zhaoyou
    if(system(cmd_rm)/*remove(cmd->arg)*/==0){
        state->message = "250 Requested file action okay, completed.\n";
    }else{
        state->message = "550 Cannot delete directory.\n";
    }

    write_state(state);
}

/** Handle SIZE (RFC 3659) */
void ftp_size(Command *cmd, State *state)
{
    if(state->logged_in){
        struct stat statbuf;
        char filesize[128];
        memset(filesize,0,128);
        /* Success */
        if(stat(cmd->arg,&statbuf)==0){
            sprintf(filesize, "213 %d\n", statbuf.st_size);
            state->message = filesize;
        }else{
            state->message = "550 Could not get file size.\n";
        }
    }else{
        state->message = "530 Please login with USER and PASS.\n";
    }
    write_state(state);
}

/** Handle RNFR */
void ftp_rnfr(Command *cmd, State *state){
    bool isAuth = auth_check(state,true,true);
    if(!isAuth){
        write_state(state);
        return;
    }else{
        struct stat statbuf;
        char *perms = malloc(9);
        memset(perms,0,9);
        char permission_message[128] = "350 Ready for RNTO";
        int operation_stat = stat(cmd->arg,&statbuf);
        printf("operation status: %d",operation_stat);
        if(operation_stat!=-1){
            str_perm((statbuf.st_mode & ALLPERMS), perms);
            strcat(permission_message, perms);
            strcat(permission_message,"\n");
            state->oldName = malloc(20);
            strcpy(state->oldName,cmd->arg);
            state->message = permission_message;
        }else{
            state->message = "550 Cannot find the file.\n";
        }
        free(perms);
    }
    write_state(state);
}

/** Handle RNTO */
void ftp_rnto(Command *cmd, State *state){
    bool isAuth = auth_check(state,true,true);

    if(!isAuth){
        write_state(state);
        return;
    }else if(state->oldName == NULL){
        state->message ="550 Requested action not taken. File unavailable.\n";
        write_state(state);
        return;
    }

    if(rename(state->oldName,cmd->arg) == 0){
        state->message = "250 Rename Successful\n";
    }else{
        state->message = "550 Requested action not taken. Rename failed.\n";
    }
    state->oldName = NULL;
    write_state(state);
}

/** Handle NOOP */
void ftp_noop(Command *cmd, State *state){
    if(state->logged_in){
        state->message = "200 Nice to NOOP you!\n";
    }else{
        state->message = "530 NOOB hehe.\n";
    }
    write_state(state);
}

/**
* Converts permissions to string. e.g. rwxrwxrwx
* @param perm Permissions mask
* @param str_perm Pointer to string representation of permissions
*/
void str_perm(int perm, char *str_perm)
{
    int curperm = 0;
    int flag = 0;
    int read, write, exec;

    /* Flags buffer */
    char fbuff[3];
    read = write = exec = 0;

    int i;
    for(i = 6; i>=0; i-=3){
        /* Explode permissions of user, group, others; starting with users */
        curperm = ((perm & ALLPERMS) >> i ) & 0x7;

        memset(fbuff,0,3);
        /* Check rwx flags for each*/
        read = (curperm >> 2) & 0x1;
        write = (curperm >> 1) & 0x1;
        exec = (curperm >> 0) & 0x1;

        sprintf(fbuff,"%c%c%c",read?'r':'-' ,write?'w':'-', exec?'x':'-');
        strcat(str_perm,fbuff);

    }
}

bool auth_check(State *state, bool isRootNeed, bool isLoginNeed){
    if(isRootNeed){
        if(strcmp(state->username,"root")!=0) {
            state->message = "531 Not root user\n";
            return false;
        }
    }

    if(isLoginNeed) {
        if(!state->logged_in){
            state->message = "530 Please login first.\n";
            return false;
        }
    }

    return true;
}

double calc_time_interval_ms(struct timeval *start_time, struct timeval  *end_time){
    struct timeval stt = *start_time, edt = *end_time;
    double timeuse = 1000000*(edt.tv_sec - stt.tv_sec) + edt.tv_usec - stt.tv_usec;
    return timeuse/1000 ;
}

void print_traffic_info(double time_ms , int traffic_byte){
    printf("Time consumed: %lf ms\n Traffic: %d bytes\n Speed: %lf KB/s\n",time_ms, traffic_byte, traffic_byte/time_ms);
}

/**
 * 1. Check the existence of the dir.
 * 2. If exist, not create.
 * 3. If not exist, create the dir.
 * @param dir
 * @return status value, 0 success. -1 fail.
 */
int mk_dir(char *dir){
    DIR *mydir = NULL;
    if((mydir= opendir(dir)) == NULL)// judge if the directory is null
    {
        int ret = mkdir(dir, DIR_AUTH_MODE);//create the directory
        if (ret != 0)
        {
            return -1;
        }
        printf("%s created sucess!\n", dir);
    }
    else
    {
        printf("Great! %s exist!\n", dir);
    }
    return 0;
}
/** PORT command */
void ftp_port(Command *cmd, State *state)
{
//    printf("***ftp_port1 cmd:%s mode:%d portdata:%d sockdata:%d connection:%d\n", cmd->command, state->mode, state->port_active,state->sock_active, state->connection);
    if(state->logged_in){
        int ip[4];
        char buff[255];
        char *response = "200 PORT command successful.\n";

        //get port
//        printf("***1***%s\n", cmd->arg);
        char* p = cmd->arg;
        char ipx[20] = "";
        int i;
        for(i = 0; i < 4; i++){ // 处理收到的 IP 地址
            if(i) strcat(ipx, "."); // 追加點.
            strncat(ipx, p, strchr(p, ',') - p);    //在
            p = strchr(p, ',') + 1;
        }

        Port *port = malloc(sizeof(Port));
        gen_port2(p, port);
        getip(state->connection,ip);

        /* Start creating socket here, but don't bind,  -> connect client */
        state->port_active = (256*port->p1)+port->p2;

        state->sock_active = set_socket(20, *state);
        sprintf(buff,response);
        state->message = buff;
        state->mode = CLIENT;
//        printf("***ftp_port9 mode:%d portdata:%d sockdata:%d connection:%d\n", state->mode, state->port_active,state->sock_active, state->connection);
//        puts(state->message);

    }else{
        state->message = "530 Please login with USER and PASS.\n";
        printf("%s",state->message);
    }
    write_state(state);
}

void list_proc(DIR *dp,int *connection )
{
    struct dirent *entry;
    struct stat statbuf;
    time_t rawtime;
    int data_traffic = 0;
    struct timeval start_time, end_time;
    char timebuff[80], current_dir[BSIZE];
    struct tm *time;

    //start timing for sending
    gettimeofday(&start_time,0);

    while(entry=readdir(dp)){
        if(stat(entry->d_name,&statbuf)==-1){
            fprintf(stderr, "FTP: Error reading file stats...\n");
        }else{
            char *perms = malloc(9);
            memset(perms,0,9);

            /* Convert time_t to tm struct */
            rawtime = statbuf.st_mtime;
            time = localtime(&rawtime);
            strftime(timebuff,80,"%b %d %H:%M",time);
            str_perm((statbuf.st_mode & ALLPERMS), perms);
            data_traffic += dprintf(connection,
                                    "%c%s %5d %4d %4d %8d %s %s\r\n",
                                    (entry->d_type==DT_DIR)?'d':'-',
                                    perms,statbuf.st_nlink,
                                    statbuf.st_uid,
                                    statbuf.st_gid,
                                    statbuf.st_size,
                                    timebuff,
                                    entry->d_name);
            free(perms);
        }

    }
    //stop timing for sending
    gettimeofday(&end_time,0);
    //Process the time
    double timeuse = calc_time_interval_ms(&start_time,&end_time);
    print_traffic_info(timeuse,data_traffic);

}

int delay_download(int fd, int connection)
{
    int pipefd[2];
    const int buff_size = 100;
    if(pipe(pipefd)==-1)perror("ftp_stor: pipe");
    int res;
    int sent_total=0, traffic= 0;

    struct timeval start_time, end_time;

    gettimeofday(&start_time,0);
    while (  res = splice( fd, 0, pipefd[1], NULL, buff_size, SPLICE_F_MORE | SPLICE_F_MOVE)  >0){
        traffic+= splice( pipefd[0], NULL, connection, 0, buff_size, SPLICE_F_MORE | SPLICE_F_MOVE);
        usleep(10000);
    }
    //stop timing for sending
    gettimeofday(&end_time,0);
    //Process the time
    double timeuse = calc_time_interval_ms(&start_time,&end_time);
    print_traffic_info(timeuse,traffic);

    return res;
}

int delay_upload(int fd, int connection)
{
    return delay_download(connection,fd);
}